package com.qqt.csr.common.idempotent;

import com.qqt.csr.common.exception.BaseException;
import com.qqt.csr.common.exception.StatusCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author:
 * @date: 2024-07-04 14:54
 */
@Aspect
@Component
@Order(2)
@Slf4j
public class IdempotentAspect implements InitializingBean {
    @Autowired
    private ApplicationContext applicationContext;

    private RedissonClient redissonClient;

    @Override
    public void afterPropertiesSet() throws Exception {
        redissonClient = applicationContext.getBean(RedissonClient.class);
    }
    @Pointcut("@annotation(com.qqt.csr.common.idempotent.Idempotent)")
    public void idempotentAspect() {
    }

    @Around("idempotentAspect()")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
        if (redissonClient == null) {
            return joinPoint.proceed();
        }

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        if (StringUtils.isEmpty(idempotent.keyPrefix())) {
            throw new BaseException(StatusCode.INTERNAL_SERVER_ERROR.getCode(), "重复提交前缀不能为空");
        }

        // 获取自定义key
        final String lockKey = RequestKeyGenerator.getLockKey(joinPoint);
        if(lockKey == null){
            throw new BaseException(StatusCode.INTERNAL_SERVER_ERROR.getCode(), "组装为lockKey的参数不能为空");
        }

        RLock lock = redissonClient.getLock(lockKey);
        boolean isLocked = false;
        try {
            isLocked = lock.tryLock(-1, idempotent.timeout(), idempotent.timeUnit());
            if (!isLocked) {
                throw new BaseException(idempotent.statusCode(), idempotent.message());
            }
            return joinPoint.proceed();
        } finally {
            // 释放锁
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
